﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Windows.Controls;
using WickedSick.CommonComponents.IO;

namespace WickedSick.ClientComponents.Music
{
    public delegate void MediaChangedDelegate(object sender, MediaChangedEventArgs e);
    public delegate void PlaylistEndedDelegate(object sender, EventArgs e);

    public delegate bool IsCopyLocalAllowed(long file_length);

    public class MediaChangedEventArgs : EventArgs
    {
        private Song _OldSong;
        private int _OldIndex;
        private Song _NewSong;
        private int _NewIndex;

        internal MediaChangedEventArgs(MediaChangedEventArgs copy)
        {
            this._OldSong = copy.OldSong;
            this._OldIndex = copy.OldIndex;
            this._NewSong = copy.NewSong;
            this._NewIndex = copy.NewIndex;
        }

        public MediaChangedEventArgs(Song old_song, Song new_song, int old_index, int new_index)
        {
            _OldSong = old_song;
            _OldIndex = old_index;
            _NewSong = new_song;
            _NewIndex = new_index;
        }

        public Song OldSong
        {
            get { return _OldSong; }
            set { _OldSong = value; }
        }

        public Song NewSong
        {
            get { return _NewSong; }
            set { _NewSong = value; }
        }

        public int OldIndex
        {
            get { return _OldIndex;}
            set { _OldIndex = value;}
        }

        public int NewIndex
        {
            get { return _NewIndex; }
            set { _NewIndex = value; }
        }
    }

    public class PlaybackManager
    {
        private MediaElement _Player;
        private FileSyncLocation _Location;

        private MediaChangedEventArgs _MediaEventArgs;
        public event MediaChangedDelegate MediaChanged;
        public event PlaylistEndedDelegate PlaylistEnded;
        private Song _CurrentSong;
        private Playlist _CurrentPlaylist;
        private bool _HasPlaylistChanged;

        private bool _IsRandom;
        private bool _IsRepeat;

        private int _CurrentIndex;
        private ObservableCollection<Song> _RegularList = new ObservableCollection<Song>();
        private List<Song> _RandomList;
        private List<int> _RandomIndices;

        private DirectoryInfo _BaseDirectory;
        private bool _IsCurrentLocal;

        private IsCopyLocalAllowed _CopyAllowed;

        private const string HOST = "http://sdb.wsick.com/";

        public PlaybackManager(MediaElement player, string base_directory, IsCopyLocalAllowed copy_allowed_func)
        {
            _Location = new FileSyncLocation(Environment.GetFolderPath(Environment.SpecialFolder.InternetCache));
            ((ObservableCollection<FileChange>)_Location.Changes).CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(PlaybackManager_CollectionChanged);

            _Player = player;
            _Player.LoadedBehavior = MediaState.Manual;

            base_directory.TrimEnd('\\');
            if (!Directory.Exists(base_directory))
            {
                Directory.CreateDirectory(base_directory);
            }
            _BaseDirectory = new DirectoryInfo(base_directory);

            _Player.MediaOpened += new System.Windows.RoutedEventHandler(_Player_MediaOpened);
            _Player.MediaEnded += new System.Windows.RoutedEventHandler(_Player_MediaEnded);
            _MediaEventArgs = new MediaChangedEventArgs(null, null, -1, -1);

            _HasPlaylistChanged = false;
            _CopyAllowed = copy_allowed_func;
        }

        /// <summary>
        /// Clears all songs in the playlist and clears random list as well.
        /// </summary>
        public void ClearSongs()
        {
            _CurrentIndex = -1;
            _RegularList.Clear();
            _RandomIndices.Clear();
            _RandomList.Clear();
            _CurrentPlaylist = null;
            _MediaEventArgs = new MediaChangedEventArgs(null, null, -1, -1);
            _HasPlaylistChanged = false;
        }

        /// <summary>
        /// Enqueues a song to the end of the currently playing playlist.
        /// Plays song if playlist is empty.
        /// </summary>
        /// <remarks>
        /// If random is enabled, random order will rebuild to include new songs in random list.
        /// </remarks>
        /// <param name="s">This is the new song to add.</param>
        public void EnqueueSong(Song s)
        {
            bool new_list = false;
            if (_CurrentPlaylist == null) //Enqueue then play
            {
                _CurrentPlaylist = new Playlist();
                _CurrentPlaylist.Name = "{New}";
                _CurrentPlaylist.Description = "{New Playlist}";
                new_list = true;
            }

            _CurrentPlaylist.Songs.Add(s);
            _RegularList.Add(s);

            if (new_list)
                RawPlaySong(s);

            if (_IsRandom)
                RebuildRunningPlaylist();

            _HasPlaylistChanged = true;
        }

        /// <summary>
        /// Enqueues a list of songs to the end of the currently playing playlist.
        /// </summary>
        /// <remarks>
        /// If random is enabled, random order will rebuild to include new songs in random list.
        /// </remarks>
        /// <param name="songs">This is a list of new songs to add.</param>
        public void EnqueueSongs(IList<Song> songs)
        {
            bool new_list = false;

            if (_CurrentPlaylist == null) //Enqueue then play
            {
                _CurrentPlaylist = new Playlist();
                _CurrentPlaylist.Name = "{New}";
                _CurrentPlaylist.Description = "{New Playlist}";
                new_list = true;
            }

            _CurrentPlaylist.Songs.AddRange(songs);
            foreach (Song s in songs)
            {
                _RegularList.Add(s);
            }

            if (new_list)
                RawPlaySong(songs[0]);

            if (_IsRandom)
                RebuildRunningPlaylist();
            _HasPlaylistChanged = true;
        }

        /// <summary>
        /// Advances song in playlist given inputted Song.
        /// Will stop media if song does not exist.
        /// </summary>
        /// <remarks>
        /// s.ID must match a song in the playlist.
        /// </remarks>
        /// <param name="s">This is the song to play given that the song exists in the playlist.</param>
        public void PlaySongInPlaylist(Song s)
        {
            _MediaEventArgs.OldIndex = _CurrentIndex;
            _MediaEventArgs.OldSong = _CurrentSong;

            List<Song> _CurrentList;
            if (_IsRandom)
                _CurrentList = _RandomList;
            else
                _CurrentList = _RegularList.ToList();

            bool flag = false;
            for (int i = 0; i < _CurrentList.Count; i++)
            {
                if (_CurrentList[i].ID == s.ID)
                {
                    _CurrentIndex = i;
                    flag = true;
                    break;
                }
            }

            if (flag)
            {
                RawPlaySong(s);
            }
            else
            {
                _Player.Stop();
            }
        }

        /// <summary>
        /// Advances song to song given by inputted index.
        /// Will stop media if index is out of range.
        /// </summary>
        /// <param name="index">This is the index of the song to start playing.</param>
        public void PlaySongInPlaylist(int index)
        {
            _MediaEventArgs.OldIndex = _CurrentIndex;
            _MediaEventArgs.OldSong = _CurrentSong;

            List<Song> _CurrentList;
            if (_IsRandom)
                _CurrentList = _RandomList;
            else
                _CurrentList = _RegularList.ToList();

            _CurrentIndex = index;

            if (index < _CurrentList.Count)
            {
                RawPlaySong(_CurrentList[index]);
            }
            else
            {
                _Player.Stop();
            }
        }

        /// <summary>
        /// Queues a playlist. If manager is set to random, playlist will be randomized then play first song.
        /// </summary>
        /// <param name="p">This is the playlist that will enqueue and begin playing.</param>
        public void PlayPlaylist(Playlist p)
        {
            _CurrentPlaylist = p;
            _MediaEventArgs = new MediaChangedEventArgs(null, null, -1, -1);
            _CurrentIndex = 0;

            //Assures _RegularList is loaded
            BuildRegularPlaylist();

            if (_IsRandom)
            {
                RandomizeSongList();
                RawPlaySong(_RandomList[0]);
            }
            else
            {
                RawPlaySong(_RegularList[0]);
            }
        }

        /// <summary>
        /// Enqueues the list of songs included in this playlist.
        /// </summary>
        /// <remarks>
        /// If random is enabled, random order will rebuild to include new songs in random list.
        /// </remarks>
        /// <param name="p">This is the playlist to enqueue.</param>
        public void EnqueuePlaylist(Playlist p)
        {
            EnqueueSongs(p.Songs);
            _HasPlaylistChanged = true;
        }

        /// <summary>
        /// Enqueues the list of songs from each of the inputted playlists.
        /// </summary>
        /// <remarks>
        /// If random is enabled, random order will rebuild to include new songs in random list.
        /// </remarks>
        /// <param name="playlists">This is the list of playlists from which to add songs.</param>
        public void EnqueuePlaylists(IList<Playlist> playlists)
        {
            List<Song> temp = new List<Song>();
            foreach (Playlist p in playlists)
            {
                temp.AddRange(p.Songs);
            }
            EnqueueSongs(temp);
            _HasPlaylistChanged = true;
        }

        /// <summary>
        /// Plays the song immediately before currently playing song.
        /// </summary>
        /// <remarks>
        /// If random, plays song immediately before current song in the shuffled list.
        /// </remarks>
        public void Previous()
        {
            GoBackSongInPlaylist();
        }

        /// <summary>
        /// Plays the song immediately after currently playing song.
        /// </summary>
        /// <remarks>
        /// If random, plays song immediately after current song in the shuffled list.
        /// </remarks>
        public void Next()
        {
            AdvanceSongInPlaylist(true);
        }

        /// <summary>
        /// Stops playing, but does not change currently playing song.
        /// </summary>
        public void Stop()
        {
            _Player.Stop();
        }

        /// <summary>
        /// Pauses playing no matter the state of the player.
        /// </summary>
        /// <remarks>
        /// This has no effect if already paused. See also TogglePlay().
        /// </remarks>
        public void Pause()
        {
            if (!_Player.Clock.IsPaused)
            {
                _Player.Pause();
            }
        }

        /// <summary>
        /// Plays current song no matter the state of the player.
        /// </summary>
        /// <remarks>
        /// This has no effect if already playing. See also TogglePlay().
        /// </remarks>
        public void Play()
        {
            if (_Player.Clock.IsPaused)
            {
                _Player.Play();
            }
        }

        /// <summary>
        /// Toggles state of media player between Play and Pause.
        /// </summary>
        public void TogglePlay()
        {
            if (_Player.Clock == null)
            {
                _Player.Play();
                return;
            }

            if (_Player.Clock.IsPaused)
            {
                _Player.Play();
            }
            else
            {
                _Player.Pause();
            }
        }

        #region INTERNAL EVENTS

        private void PlaybackManager_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            if (_IsCurrentLocal)
                return;

            if (_CurrentSong == null)
            {
                //Don't do anything
                return;
            }

            string temp = _CurrentSong.ParentArtist.Name.ToLower().Replace(" ", "_");
            temp += "_-_";
            temp += _CurrentSong.Title.ToLower().Replace(" ", "_");

            foreach (FileChange fc in e.NewItems)
            {
                if (fc.Filename.ToLower().Contains(temp))
                {
                    System.IO.FileInfo fi = new FileInfo(fc.Filename);
                    if (fi.Length > 0)
                    {
                        if (_Player.DownloadProgress >= 1.0)
                        {
            
                            IsCopyLocalAllowed temp2 = _CopyAllowed;
                            if (temp2 != null)
                            {
                                if (!temp2(fi.Length))
                                {
                                    break;
                                }
                            }

                            File.Copy(fc.Filename, LocalLocation, true);
                        }
                    }
                }
            }
        }

        private void _Player_MediaOpened(object sender, System.Windows.RoutedEventArgs e)
        {
            _MediaEventArgs.NewSong = _CurrentSong;
            _MediaEventArgs.NewIndex = this.CurrentSongIndex;
            CallMediaChanged();
        }

        private void _Player_MediaEnded(object sender, System.Windows.RoutedEventArgs e)
        {
            _MediaEventArgs.OldSong = _CurrentSong;
            _MediaEventArgs.OldIndex = this.CurrentSongIndex;
            AdvanceSongInPlaylist(false);
        }
        
        #endregion

        #region PLAYER HELPERS

        private void RawPlaySong(Song s)
        {
            if (s == null)
                return;

            _CurrentSong = s;
            _Player.Source = BuildURI(s);
            if (!_IsCurrentLocal)
            {
                //Copy local in file system watcher
            }
            _Player.Play();
        }

        private void GoBackSongInPlaylist()
        {
            List<Song> _TempList;
            if (_IsRandom)
            {
                _TempList = _RandomList;
            }
            else
            {
                _TempList = _RegularList.ToList();
            }

            if (_TempList == null || _TempList.Count == 0)
            {
                return;
            }

            _MediaEventArgs.OldIndex = this.CurrentSongIndex;
            _MediaEventArgs.OldSong = _CurrentSong;

            if (_CurrentIndex > 0)
            {
                _CurrentIndex--;
            }
            else
            {
                _CurrentIndex = _TempList.Count - 1;
            }
            RawPlaySong(_TempList[_CurrentIndex]);
        }

        private void AdvanceSongInPlaylist(bool manual)
        {
            List<Song> _TempList;
            if (_IsRandom)
            {
                _TempList = _RandomList;
            }
            else
            {
                _TempList = _RegularList.ToList();
            }

            if (_TempList == null || _TempList.Count == 0)
            {
                return;
            }

            _MediaEventArgs.OldIndex = this.CurrentSongIndex;
            _MediaEventArgs.OldSong = _CurrentSong;

            _CurrentIndex++;
            //Reached end, no repeat, so end the playlist
            if (!_IsRepeat && (_CurrentIndex >= _TempList.Count) && !manual)
            {
                CallPlaylistEnded();
            }
            //Reached end, wrap around because of repeat
            else if (_CurrentIndex >= _TempList.Count)
            {
                if (manual)
                {
                    _CurrentIndex = 0;
                    RawPlaySong(_TempList[_CurrentIndex]);
                }
            }
            //Next song in playlist
            else
            {
                RawPlaySong(_TempList[_CurrentIndex]);
            }
        }

        private void BuildRegularPlaylist()
        {
            _RegularList.Clear();
            if (_CurrentPlaylist == null || _CurrentPlaylist.Songs == null)
            {
                return;
            }
            foreach (Song s in _CurrentPlaylist.Songs)
            {
                _RegularList.Add(s);
            }
        }

        private void RebuildRunningPlaylist()
        {
            if (_IsRandom)
            {
                RandomizeSongList();
            }
            else
            {
                if (_RandomIndices == null)
                    _CurrentIndex = 0;
                else
                    _CurrentIndex = _RandomIndices[_CurrentIndex];
            }
        }

        private void RandomizeSongList()
        {
            if (_RegularList == null)
                return;

            List<Song> _NewList = new List<Song>();
            List<int> _NewIndices = new List<int>();
            List<Song> temp = new List<Song>(_RegularList);

            for (int i = 0; i < temp.Count; i++)
            {
                _NewList.Add(null);
                _NewIndices.Add(-1);
            }

            if (temp.Count == 1)
            {
                _NewList[0] = temp[0];
                _NewIndices[0] = 0;
            }
            else
            {
                _NewList[0] = temp[_CurrentIndex];
                _NewIndices[0] = _CurrentIndex;
                temp[_CurrentIndex] = null;
                for (int i = 1; i < _NewList.Count; i++)
                {
                    int _NewRandom;
                    Random rand = new Random();
                    do
                    {
                        _NewRandom = rand.Next(0, _NewList.Count - 1);
                    } while (temp[_NewRandom] == null);
                    _NewList[i] = temp[_NewRandom];
                    _NewIndices[i] = _NewRandom;
                    temp[_NewRandom] = null;
                }
            }
            _RandomIndices = _NewIndices;
            _RandomList = _NewList;
        }

        private Uri BuildURI(Song s)
        {
            Uri temp;
            if (File.Exists(LocalLocation))
            {
                _IsCurrentLocal = true;
                temp = new Uri(LocalLocation, UriKind.Absolute);
            }
            else
            {
                _IsCurrentLocal = false;
                temp = new Uri(RemoteLocation, UriKind.Absolute);
            }
            return temp;
        }

        #endregion

        #region PROPERTIES

        private string LocalLocation
        {
            get
            {
                string temp = "";
                temp += _BaseDirectory;
                temp += @"\";
                if (_CurrentSong.ParentArtist != null && _CurrentSong.ParentArtist.Name.Length > 0)
                {
                    temp += _CurrentSong.ParentArtist.Name;
                    temp += @"\";
                }
                if (_CurrentSong.ParentAlbum != null && _CurrentSong.ParentAlbum.Name.Length > 0)
                {
                    temp += _CurrentSong.ParentAlbum.Name;
                    temp += @"\";
                }

                if (!Directory.Exists(temp))
                {
                    Directory.CreateDirectory(temp);
                }

                temp += _CurrentSong.Title;
                temp += ".mp3";
                return temp;
            }
        }

        private string RemoteLocation
        {
            get
            {
                string temp = "";
                temp += HOST;
                temp += "songs/";
                if (_CurrentSong.ParentArtist != null && _CurrentSong.ParentArtist.Name.Length > 0)
                {
                    temp += _CurrentSong.ParentArtist.Name;
                    temp += "/";
                }
                if (_CurrentSong.ParentAlbum != null && _CurrentSong.ParentAlbum.Name.Length > 0)
                {
                    temp += _CurrentSong.ParentAlbum.Name;
                    temp += "/";
                }
                if (_CurrentSong.ParentArtist != null && _CurrentSong.ParentArtist.Name.Length > 0)
                {
                    temp += _CurrentSong.ParentArtist.Name;
                    temp += " - ";
                }
                temp += _CurrentSong.Title;
                temp += ".mp3";
                return temp;
            }
        }

        public bool IsRandom
        {
            get { return _IsRandom; }
            set 
            {
                if (value != _IsRandom)
                {
                    _IsRandom = value;
                    RebuildRunningPlaylist();
                }
                else
                {
                    _IsRandom = value;
                }
            }
        }

        public bool IsRepeat
        {
            get { return _IsRepeat; }
            set { _IsRepeat = value; }
        }

        public bool HasPlaylistChanged
        {
            get { return _HasPlaylistChanged; }
        }

        public IsCopyLocalAllowed CopyAllowedFunction
        {
            get { return _CopyAllowed; }
            set { _CopyAllowed = value; }
        }

        public ObservableCollection<Song> SongList
        {
            get { return _RegularList; }
        }

        public int CurrentSongIndex
        {
            get
            {
                return (_IsRandom) ? _RandomIndices[_CurrentIndex] : _CurrentIndex;
            }
        }

        public Song CurrentSong
        {
            get { return _CurrentSong; }
        }

        #endregion

        #region EVENT CALLS

        private void CallMediaChanged()
        {
            MediaChangedDelegate temp = MediaChanged;
            if (temp != null)
            {
                temp(this, new MediaChangedEventArgs(_MediaEventArgs));
            }
        }

        private void CallPlaylistEnded()
        {
            PlaylistEndedDelegate temp = PlaylistEnded;
            if (temp != null)
            {
                temp(this, new EventArgs());
            }
        }

        #endregion
    }
}
